#if !NETSTANDARD1_3
using System.Drawing;
using QRCoder.Extensions;
using static QRCoder.QRCodeGenerator;
using static QRCoder.SvgQRCode;

namespace QRCoder;

/// <summary>
/// Represents a QR code generator that outputs QR codes as SVG images.
/// </summary>
public class SvgQRCode : AbstractQRCode, IDisposable
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SvgQRCode"/> class.
    /// Constructor without parameters to be used in COM objects connections.
    /// </summary>
    public SvgQRCode() { }

    /// <summary>
    /// Initializes a new instance of the <see cref="SvgQRCode"/> class with the specified <see cref="QRCodeData"/>.
    /// </summary>
    /// <param name="data"><see cref="QRCodeData"/> generated by the QRCodeGenerator.</param>
    public SvgQRCode(QRCodeData data) : base(data) { }

    /// <summary>
    /// Returns a scalable QR code as an SVG string.
    /// </summary>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic()
    {
        return GetGraphic(Size.Empty, Color.Black, Color.White, true, SizingMode.ViewBoxAttribute, null);
    }

    /// <summary>
    /// Returns a QR code as an SVG string.
    /// </summary>
    /// <param name="pixelsPerModule">The pixel size each dark/light module is drawn.</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(int pixelsPerModule)
    {
        var viewBox = new Size(pixelsPerModule * QrCodeData.ModuleMatrix.Count, pixelsPerModule * QrCodeData.ModuleMatrix.Count);
        return GetGraphic(viewBox, Color.Black, Color.White);
    }

    /// <summary>
    /// Returns a QR code as an SVG string with custom colors, optional quiet zones, and an optional logo.
    /// </summary>
    /// <param name="pixelsPerModule">The pixel size each dark/light module is drawn; applicable only when <paramref name="sizingMode"/> is <see cref="SizingMode.WidthHeightAttribute"/>.</param>
    /// <param name="darkColor">The color of the dark modules.</param>
    /// <param name="lightColor">The color of the light modules.</param>
    /// <param name="drawQuietZones">If true, a white border is drawn around the entire QR code.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
    {
        var offset = drawQuietZones ? 0 : 4;
        var edgeSize = QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule);
        var viewBox = new Size(edgeSize, edgeSize);
        return GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, sizingMode, logo);
    }

    /// <summary>
    /// Returns a QR code as an SVG string with custom colors (in HEX syntax), optional quiet zones, and an optional logo.
    /// </summary>
    /// <param name="pixelsPerModule">The pixel size each dark/light module is drawn; applicable only when <paramref name="sizingMode"/> is <see cref="SizingMode.WidthHeightAttribute"/>.</param>
    /// <param name="darkColorHex">The color of the dark/black modules in HEX format (e.g., #000000).</param>
    /// <param name="lightColorHex">The color of the light/white modules in HEX format (e.g., #ffffff).</param>
    /// <param name="drawQuietZones">If true, a white border is drawn around the entire QR code.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
    {
        var offset = drawQuietZones ? 0 : 4;
        var edgeSize = QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule);
        var viewBox = new Size(edgeSize, edgeSize);
        return GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo);
    }

    /// <summary>
    /// Returns a QR code as an SVG string with optional quiet zones and an optional logo.
    /// </summary>
    /// <param name="viewBox">The width and height for the SVG when <paramref name="sizingMode"/> is <see cref="SizingMode.WidthHeightAttribute"/>; unused otherwise.</param>
    /// <param name="drawQuietZones">If true, a white border is drawn around the entire QR code.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
        => GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, sizingMode, logo);

    /// <summary>
    /// Returns a QR code as an SVG string with custom colors and optional quiet zones and an optional logo.
    /// </summary>
    /// <param name="viewBox">The width and height for the SVG when <paramref name="sizingMode"/> is <see cref="SizingMode.WidthHeightAttribute"/>; unused otherwise.</param>
    /// <param name="darkColor">The color of the dark modules.</param>
    /// <param name="lightColor">The color of the light modules.</param>
    /// <param name="drawQuietZones">If true, a white border is drawn around the entire QR code.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
        => GetGraphic(viewBox, ColorToHex(darkColor), ColorToHex(lightColor), drawQuietZones, sizingMode, logo);

    /// <summary>
    /// Returns a QR code as an SVG string with custom colors (in HEX syntax), optional quiet zones, and an optional logo.
    /// </summary>
    /// <param name="viewBox">The width and height for the SVG when <paramref name="sizingMode"/> is <see cref="SizingMode.WidthHeightAttribute"/>; unused otherwise.</param>
    /// <param name="darkColorHex">The color of the dark/black modules in HEX format (e.g., #000000).</param>
    /// <param name="lightColorHex">The color of the light/white modules in HEX format (e.g., #ffffff).</param>
    /// <param name="drawQuietZones">If true, a white border is drawn around the entire QR code.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
    {
        int offset = drawQuietZones ? 0 : 4;
        int drawableModulesCount = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2);

        // Build SVG opening tag with size attributes
        var svgFile = new StringBuilder();

        svgFile.Append("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ");
        svgFile.AppendInvariant(drawableModulesCount);
        svgFile.Append(' ');
        svgFile.AppendInvariant(drawableModulesCount);
        svgFile.Append("\" shape-rendering=\"crispEdges\"");

        // Add xlink namespace if logo is used (to retain compatibility with older SVG viewers)
        if (logo != null && !logo.IsEmbedded())
        {
            svgFile.Append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
        }

        // Add width/height attributes if specified by sizing mode
        if (sizingMode == SizingMode.WidthHeightAttribute)
        {
            svgFile.Append(" width=\"");
            svgFile.AppendInvariant(viewBox.Width);
            svgFile.Append("\" height=\"");
            svgFile.AppendInvariant(viewBox.Height);
            svgFile.Append('"');
        }
        svgFile.Append('>');
        svgFile.AppendLine();

        // Determine if we need to draw light modules as a path or as a background
        bool drawLightModulesAsPath = IsPartiallyTransparent(darkColorHex);

        // Draw light color background if not transparent and dark is fully opaque
        if (!IsFullyTransparent(lightColorHex) && !drawLightModulesAsPath)
        {
            svgFile.Append("<rect x=\"0\" y=\"0\" width=\"");
            svgFile.AppendInvariant(drawableModulesCount);
            svgFile.Append("\" height=\"");
            svgFile.AppendInvariant(drawableModulesCount);
            svgFile.Append("\" fill=\"");
            svgFile.Append(lightColorHex);
            svgFile.AppendLine("\"/>");
        }

        // Calculate logo attributes if needed (in module coordinates)
        RectangleF? logoAttr = null;
        if (logo != null)
        {
            logoAttr = GetLogoAttributes(logo, new Size(drawableModulesCount, drawableModulesCount));
        }

        // Draw light modules as path if dark is not fully opaque
        if (!IsFullyTransparent(lightColorHex) && drawLightModulesAsPath)
        {
            DrawModulesPath(false, lightColorHex);
        }

        // Draw dark modules if not fully transparent
        if (!IsFullyTransparent(darkColorHex))
        {
            DrawModulesPath(true, darkColorHex);
        }

        // Render logo, if set
        if (logo != null)
        {

            if (!logo.IsEmbedded())
            {
                svgFile.Append("<image x=\"");
                svgFile.AppendInvariant(logoAttr!.Value.X);
                svgFile.Append("\" y=\"");
                svgFile.AppendInvariant(logoAttr.Value.Y);
                svgFile.Append("\" width=\"");
                svgFile.AppendInvariant(logoAttr.Value.Width);
                svgFile.Append("\" height=\"");
                svgFile.AppendInvariant(logoAttr.Value.Height);
                svgFile.Append("\" xlink:href=\"");
                svgFile.Append(logo.GetDataUri());
                svgFile.AppendLine("\"/>");
            }
            else
            {
                var rawLogo = (string)logo.GetRawLogo();
                var svg = System.Xml.Linq.XDocument.Parse(rawLogo);
                svg.Root!.SetAttributeValue("x", CleanSvgVal(logoAttr!.Value.X));
                svg.Root.SetAttributeValue("y", CleanSvgVal(logoAttr.Value.Y));
                svg.Root.SetAttributeValue("width", CleanSvgVal(logoAttr.Value.Width));
                svg.Root.SetAttributeValue("height", CleanSvgVal(logoAttr.Value.Height));
                svg.Root.SetAttributeValue("shape-rendering", "geometricPrecision");
                svgFile.AppendLine(svg.ToString(System.Xml.Linq.SaveOptions.DisableFormatting).Replace("svg:", ""));
            }
        }

        svgFile.Append(@"</svg>");
        return svgFile.ToString();

        // Local function to draw modules as a path
        void DrawModulesPath(bool drawDarkModules, string colorHex)
        {
            svgFile.Append("<path fill=\"");
            svgFile.Append(colorHex);
            svgFile.Append("\" d=\"");

            // Build path using RLE to combine rectangles
            for (int y = 0; y < drawableModulesCount; y++)
            {
                int x = 0;

                while (x < drawableModulesCount)
                {
                    if (!ShouldDrawModule(x, y))
                    {
                        x++;
                        continue;
                    }

                    // Found a module to draw - find the run length
                    int startX = x;
                    x++;

                    while (x < drawableModulesCount && ShouldDrawModule(x, y))
                    {
                        x++;
                    }

                    int width = x - startX;
                    if (width > 0)
                    {
                        // Absolute move to start of rectangle
                        svgFile.Append('M');
                        svgFile.AppendInvariant(startX);
                        svgFile.Append(' ');
                        svgFile.AppendInvariant(y);

                        // Draw rectangle using relative movements (width, height of 1)
                        svgFile.Append('h');
                        svgFile.AppendInvariant(width);
                        svgFile.Append("v1h-");
                        svgFile.AppendInvariant(width);
                        svgFile.Append('z');
                    }
                }
            }

            svgFile.AppendLine("\"/>");

            // Local function to determine if a module should be drawn
            bool ShouldDrawModule(int x, int y)
            {
                bool isDarkModule = QrCodeData.ModuleMatrix[y + offset][x + offset];
                bool isBlockedByLogo = logo != null && logo.FillLogoBackground() &&
                    IsBlockedByLogo(x, y, logoAttr!.Value);

                // For dark modules: draw only dark modules not blocked by logo
                // For light modules: draw light modules not blocked by logo, or anywhere blocked by logo (regardless of module color)
                return drawDarkModules
                    ? (isDarkModule && !isBlockedByLogo)
                    : (!isDarkModule || isBlockedByLogo);
            }
        }
    }

    /// <summary>
    /// Determines if a module at (x,y) is blocked by the logo area defined by attr.
    /// </summary>
    private static bool IsBlockedByLogo(int x, int y, RectangleF attr)
    {
        return
            x + 1 > attr.X &&             // Right edge of module > left edge of logo
            x < attr.X + attr.Width &&    // Left edge of module < right edge of logo
            y + 1 > attr.Y &&             // Bottom edge of module > top edge of logo
            y < attr.Y + attr.Height;     // Top edge of module < bottom edge of logo
    }

    /// <summary>
    /// Calculates the logo's position and size within the QR code based on the specified percentage size.
    /// </summary>
    private static RectangleF GetLogoAttributes(SvgLogo logo, Size viewBox)
    {
        var imgWidth = logo.GetIconSizePercent() * viewBox.Width / 100f;
        var imgHeight = logo.GetIconSizePercent() * viewBox.Height / 100f;
        var imgPosX = viewBox.Width / 2f - imgWidth / 2f;
        var imgPosY = viewBox.Height / 2f - imgHeight / 2f;
        return new RectangleF(imgPosX, imgPosY, imgWidth, imgHeight);
    }

    //Clean double values for international use/formats
    //We use explicitly "G7" to avoid differences between .NET full and Core platforms
    //https://stackoverflow.com/questions/64898117/tostring-has-a-different-behavior-between-net-462-and-net-core-3-1
    private static string CleanSvgVal(float input)
        => input.ToString("G7", CultureInfo.InvariantCulture);

    /// <summary>
    /// Gets the transparency value (0-255) from a color string.
    /// </summary>
    /// <param name="colorHex">The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent".</param>
    /// <returns>The alpha/transparency value from 0 (fully transparent) to 255 (fully opaque). Returns 255 for colors without alpha channel.</returns>
    private static int GetTransparency(string colorHex)
    {
        if (string.IsNullOrEmpty(colorHex))
            throw new ArgumentNullException(nameof(colorHex), "Please specify a color using a hex format such as #RRGGBB or the string 'transparent'.");

        // Check for literal "transparent" keyword
        if (colorHex.Equals("transparent", StringComparison.OrdinalIgnoreCase))
            return 0; // Fully transparent

        // Check for hex color with alpha channel
        if (colorHex.StartsWith('#'))
        {
            // #RRGGBBAA format (9 characters)
            if (colorHex.Length == 9)
            {
                // Extract alpha channel (last 2 characters)
#if HAS_SPAN
                if (int.TryParse(colorHex.AsSpan(7, 2), NumberStyles.HexNumber, null, out int alpha))
#else
                if (int.TryParse(colorHex.Substring(7, 2), NumberStyles.HexNumber, null, out int alpha))
#endif
                {
                    return alpha;
                }
            }
            // #RGBA format (5 characters)
            else if (colorHex.Length == 5)
            {
                // Extract alpha channel (last character) and multiply by 17 to convert 4-bit to 8-bit
#if HAS_SPAN
                if (int.TryParse(colorHex.AsSpan(4, 1), NumberStyles.HexNumber, null, out int alpha))
#else
                if (int.TryParse(colorHex.Substring(4, 1), NumberStyles.HexNumber, null, out int alpha))
#endif
                {
                    return alpha * 17;
                }
            }
        }

        return 255; // Fully opaque by default
    }

    /// <summary>
    /// Determines if a color string represents a partially transparent color.
    /// </summary>
    /// <param name="colorHex">The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent".</param>
    /// <returns>True if the color is transparent; otherwise false.</returns>
    private static bool IsPartiallyTransparent(string colorHex)
        => GetTransparency(colorHex) < 255;

    /// <summary>
    /// Determines if a color string represents a fully transparent color.
    /// </summary>
    /// <param name="colorHex">The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent".</param>
    /// <returns>True if the color is fully transparent; otherwise false.</returns>
    private static bool IsFullyTransparent(string colorHex)
        => GetTransparency(colorHex) == 0;

    /// <summary>
    /// Converts a Color to a hex string in #RGB, #RRGGBB or #RRGGBBAA format; or 'transparent' for fully transparent colors.
    /// </summary>
    /// <param name="color">The color to convert.</param>
    /// <returns>A hex string representation of the color.</returns>
    private static string ColorToHex(Color color)
    {
        if (color == Color.Black)
        {
            // Use shorthand #000 for black
            return "#000";
        }
        else if (color == Color.White)
        {
            // Use shorthand #FFF for white
            return "#FFF";
        }
        else if (color.A == 255)
        {
            // Fully opaque - use #RRGGBB format
            return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
        }
        else if (color.A == 0)
        {
            // Fully transparent - use "transparent" keyword
            return "transparent";
        }
        else
        {
            // Has transparency - use #RRGGBBAA format
            return $"#{color.R:X2}{color.G:X2}{color.B:X2}{color.A:X2}";
        }
    }

    /// <summary>
    /// Mode of sizing attribute on SVG root node
    /// </summary>
    public enum SizingMode
    {
        /// <summary>
        /// Specifies width and height attributes for SVG sizing
        /// </summary>
        WidthHeightAttribute = 0,

        /// <summary>
        /// Width and height are not included within the SVG tag; viewBox is scaled to fit the container
        /// </summary>
        ViewBoxAttribute = 1,
    }

    /// <summary>
    /// Represents a logo graphic that can be rendered on a SvgQRCode
    /// </summary>
    public class SvgLogo
    {
        private readonly string _logoData;
        private readonly MediaType _mediaType;
        private readonly int _iconSizePercent;
        private readonly bool _fillLogoBackground;
        private readonly object _logoRaw;
        private readonly bool _isEmbedded;

#if SYSTEM_DRAWING
        /// <summary>
        /// Create a logo object to be used in SvgQRCode renderer
        /// </summary>
        /// <param name="iconRasterized">Logo to be rendered as Bitmap/rasterized graphic</param>
        /// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
        /// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
#if NET6_0_OR_GREATER
        [System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
        public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true)
        {
            _iconSizePercent = iconSizePercent;
            using (var ms = new System.IO.MemoryStream())
            {
                using var bitmap = new Bitmap(iconRasterized);
                bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                _logoData = Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length, Base64FormattingOptions.None);
            }
            _mediaType = MediaType.PNG;
            _fillLogoBackground = fillLogoBackground;
            _logoRaw = iconRasterized;
            _isEmbedded = false;
        }
#endif

        /// <summary>
        /// Create a logo object to be used in SvgQRCode renderer
        /// </summary>
        /// <param name="iconVectorized">Logo to be rendered as SVG/vectorized graphic/string</param>
        /// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
        /// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
        /// <param name="iconEmbedded">If true, the logo will embedded as native svg instead of embedding it as image-tag</param>
        public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true, bool iconEmbedded = true)
        {
            _iconSizePercent = iconSizePercent;
            _logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None);
            _mediaType = MediaType.SVG;
            _fillLogoBackground = fillLogoBackground;
            _logoRaw = iconVectorized;
            _isEmbedded = iconEmbedded;
        }

        /// <summary>
        /// Create a logo object to be used in SvgQRCode renderer
        /// </summary>
        /// <param name="iconRasterized">Logo to be rendered as PNG</param>
        /// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
        /// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
        public SvgLogo(byte[] iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true)
        {
            _iconSizePercent = iconSizePercent;
            _logoData = Convert.ToBase64String(iconRasterized, Base64FormattingOptions.None);
            _mediaType = MediaType.PNG;
            _fillLogoBackground = fillLogoBackground;
            _logoRaw = iconRasterized;
            _isEmbedded = false;
        }

        /// <summary>
        /// Returns the raw logo's data
        /// </summary>
        public object GetRawLogo() => _logoRaw;

        /// <summary>
        /// Defines, if the logo shall be natively embedded.
        /// true=native svg embedding, false=embedding via image-tag
        /// </summary>
        public bool IsEmbedded() => _isEmbedded;

        /// <summary>
        /// Returns the media type of the logo
        /// </summary>
        /// <returns></returns>
        public MediaType GetMediaType() => _mediaType;

        /// <summary>
        /// Returns the logo as data-uri
        /// </summary>
        public string GetDataUri()
            => $"data:{GetMimeType(_mediaType)};base64,{_logoData}";

        /// <summary>
        /// Returns how much of the QR code should be covered by the logo (in percent)
        /// </summary>
        public int GetIconSizePercent() => _iconSizePercent;

        /// <summary>
        /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo)
        /// </summary>
        public bool FillLogoBackground() => _fillLogoBackground;

        /// <summary>
        /// Media types for SvgLogos
        /// </summary>
        public enum MediaType : int
        {
            /// <summary>
            /// Portable Network Graphics (PNG) image format
            /// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
            [StringValue("image/png")]
#pragma warning restore CS0618 // Type or member is obsolete
            PNG = 0,

            /// <summary>
            /// Scalable Vector Graphics (SVG) image format
            /// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
            [StringValue("image/svg+xml")]
#pragma warning restore CS0618 // Type or member is obsolete
            SVG = 1
        }

        private static string GetMimeType(MediaType type) => type switch
        {
            MediaType.PNG => "image/png",
            MediaType.SVG => "image/svg+xml",
            _ => throw new ArgumentOutOfRangeException(nameof(type)),
        };

    }
}

/// <summary>
/// Provides static methods for creating SVG QR codes.
/// </summary>
public static class SvgQRCodeHelper
{
    /// <summary>
    /// Creates an SVG QR code with a single function call.
    /// </summary>
    /// <param name="plainText">The text or payload to be encoded inside the QR code.</param>
    /// <param name="pixelsPerModule">The pixel size each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <param name="darkColorHex">The color of the dark modules in HEX format (e.g., #000000).</param>
    /// <param name="lightColorHex">The color of the light modules in HEX format (e.g., #ffffff).</param>
    /// <param name="eccLevel">The level of error correction data.</param>
    /// <param name="forceUtf8">Specifies whether the generator should be forced to work in UTF-8 mode.</param>
    /// <param name="utf8BOM">Specifies whether the byte-order-mark should be used.</param>
    /// <param name="eciMode">Specifies which ECI mode should be used.</param>
    /// <param name="requestedVersion">Sets the fixed QR code target version.</param>
    /// <param name="drawQuietZones">Indicates if quiet zones around the QR code should be drawn.</param>
    /// <param name="sizingMode">Defines whether width/height or viewBox should be used for size definition.</param>
    /// <param name="logo">An optional logo to be rendered on the code (either Bitmap or SVG).</param>
    /// <returns>Returns the QR code graphic as an SVG string.</returns>
    public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorHex, string lightColorHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null)
    {
        using var qrGenerator = new QRCodeGenerator();
        using var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion);
        using var qrCode = new SvgQRCode(qrCodeData);
        return qrCode.GetGraphic(pixelsPerModule, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo);
    }
}

#endif
